In this version of MyRent, we introduce a range of UI Widgets to evolve the UX into something more useful. These widgets will be 'active', meaning that the host Activity will be intercepting and responding to events the user may generate when interacting with the application.
At the end of this topic our goal is to have a screen looking something like this:
We will have added:
In the previous lab we introduced a listener for the geolocation input (the latitude-longitude string).
In this lab we shall:
Continue building the MyRent app that you commenced in the previous lab.
Before we start to expand the project, we need to perform some rearranging so it can be extended in an orderly manner.
This is our current application workspace:
.. and this is a version we used in writing this lab:
Here is a detailed description on how this may be achieved:
Select gear icon and untick Compact Empty Middle Packages. Observe the change to the package layout.
Select the myrent package and create a package named activities within org.wit.myrent.
Drag and drop MyRentActivity into this new package.
Create a package named models within org.wit.myrent.
Drag and drop Residence into this new package.
Ensure that when the refactoring is complete you tick this menu item again resulting in the required layout as shown in Figure 2 above and again here in Figure 7.
Before proceeding, use the menu refactor command to rename MyRentActivity to ResidenceAcivity.
The previous iteration has one UI control, an EditText.
This iteration shall introduce:
Button
Select activity_myrent.xml, right click and using context menu commands Refactor | Rename change the name to activity_residence.xml.
Open activity_residence.xml.
Remove background, text and hint colouring (if not already done). This was introduced simply to demonstrate how it might be achieved. The refactored file is as follows:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.wit.myrent.activities.ResidenceAcivity">
<EditText
android:id="@+id/geolocation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="16dp"
android:hint="@string/geolocation_hint"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" />
</android.support.constraint.ConstraintLayout>
A number of different approaches are available to change the layout.
In Design View, drag a Button onto the MyRent canvas. Rezise, position and rename it as shown in Figure 1.
Change the left and right margins for the geolocation EditText field to be 16dp also.
You can see in Figure 1, that the text on the Button is Button. We want to replace this with a string resource called "registration_date", whose value is currently the empty string ("").
Here is the resulting strings.xml file:
<resources>
<string name="app_name">MyRent</string>
<string name="hello_world">Hello World!</string>
<string name="geolocation_hint">52.253456,-7.187162</string>
<string name="registration_date" />
</resources>
Open activity_residence.xml in Text mode. Your XML should look something like this:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.wit.myrent.activities.ResidenceAcivity">
<EditText
android:id="@+id/geolocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:hint="@string/geolocation_hint"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/registration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:text="@string/registration_date"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/geolocation" />
</android.support.constraint.ConstraintLayout>
When run, your activity should look like this:
We want to change to LinearLayout, so return to acivity_residence.xml and manually refactor your ConstraintLayout to be a LinearLayout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:orientation="vertical">
<EditText
android:id="@+id/geolocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/geolocation_hint"/>
<Button
android:id="@+id/registration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Observe that a button id has now been generated in the R.java file:
Next we shall add a section label and divider immediately before the geolocation node.
Then inspect the code in the xml editor.
In res/values/string.xml, the following string resource should have been added:
<string name="location">Location</string>
Here is the completed xml node for the location label:
<TextView
android:id="@+id/locationLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/location"
style="?android:listSeparatorTextViewStyle"/>
Implement these modifications and inspect the result in the Graphical Layout. You should be presented with that shown in Figure 5:
Finally, in this step, add a section label for status.
Here is the xml:
<TextView
android:id="@+id/statusLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status"
style="?android:listSeparatorTextViewStyle"/>
Add the referenced string resource status in res/values/strings.xml:
<string name="status">Status</string>
The result is shown in Figure 6.
We shall continue with the development of the layout in the following steps.
Here is the refactored activity_residence layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/locationLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/location"
style="?android:listSeparatorTextViewStyle"/>
<EditText
android:id="@+id/geolocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/geolocation_hint"/>
<TextView
android:id="@+id/statusLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status"
style="?android:listSeparatorTextViewStyle"/>
<Button
android:id="@+id/registration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
We shall now complete remaining work on the layout using the Graphical Layout and the Outline panel.
Here is the the layout at this stage of development. Note that we added some comments and requested focus for the geolocation field:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:orientation="vertical">
<!-- LOCATION SEPARATOR -->
<TextView
android:id="@+id/locationLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/location"
style="?android:listSeparatorTextViewStyle"/>
<!-- Geolocation (GPS Coords) -->
<EditText
android:id="@+id/geolocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/geolocation_hint">
<requestFocus/>
</EditText>
<!-- STATUS SEPARATOR-->
<TextView
android:id="@+id/statusLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status"
style="?android:listSeparatorTextViewStyle"/>
<Button
android:id="@+id/registration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
It remains only to add the checkbox.
With the Graphical Layout open, drag a CheckBox from the Widgets pane and drop directly underneath the registration_date button.
Replace android:text attribute with a string, isrented, referenced in strings.xml and change the android:id, all as shown here:
<CheckBox
android:id="@+id/isrented"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/isrented"
android:checked="false"/>
Add a string resource for the checkbox:
<string name="isrented">Rented?</string>
Because we have moved ResidenceActivity to a new folder, check the manifest file to make sure it was updated (it should have been during the refactoring process):
<activity android:name=".activities.ResidenceAcivity">
The hierarchical arrangement of the layout is shown here in Figure 3.
You will notice that the margins are <?xml version="1.0" encoding="utf-8"?>
<!-- LOCATION SEPARATOR -->
<TextView
android:id="@+id/locationLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/location"
style="?android:listSeparatorTextViewStyle"/>
<!-- Geolocation (GPS Coords) -->
<EditText
android:id="@+id/geolocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/geolocation_hint">
<requestFocus/>
</EditText>
<!-- STATUS SEPARATOR-->
<TextView
android:id="@+id/statusLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status"
style="?android:listSeparatorTextViewStyle"/>
<Button
android:id="@+id/registration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"/>
<CheckBox
android:id="@+id/isrented"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/isrented"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:checked="false"/>
And the completed XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- LOCATION SEPARATOR -->
<TextView
android:id="@+id/locationLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/location"
style="?android:listSeparatorTextViewStyle"/>
<!-- Geolocation (GPS Coords) -->
<EditText
android:id="@+id/geolocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:hint="@string/geolocation_hint">
<requestFocus/>
</EditText>
<!-- STATUS SEPARATOR-->
<TextView
android:id="@+id/statusLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status"
style="?android:listSeparatorTextViewStyle"/>
<Button
android:id="@+id/registration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"/>
<CheckBox
android:id="@+id/isrented"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/isrented"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:checked="false"/>
</LinearLayout>
Replace your Residence class with the following:
package org.wit.myrent.models;
import java.util.Date;
import java.util.Random;
public class Residence
{
public Long id;
public Long date;
//a latitude longitude pair
//example "52.4566,-6.5444"
private String geolocation;
public boolean rented;
public Residence()
{
id = unsignedLong();
date = new Date().getTime();
}
/**
* Generate a long greater than zero
* @return Unsigned Long value greater than zero
*/
private Long unsignedLong() {
long rndVal = 0;
do {
rndVal = new Random().nextLong();
} while (rndVal <= 0);
return rndVal;
}
public void setGeolocation(String geolocation)
{
this.geolocation = geolocation;
}
public String getGeolocation()
{
return geolocation;
}
public String getDateString() {
return "Registered:" + dateString();
}
private String dateString() {
String dateFormat = "EEE d MMM yyyy H:mm";
return android.text.format.DateFormat.format(dateFormat, date).toString();
}
}
Note that we have made the fields public for convenience. Also, we have introduced a new date and rented fields into the model.
In the ResidenceActivity class, introduce 2 new fields to access the new widgets we have just introduced:
private CheckBox rented;
private Button dateButton;
and on OnCreate, we need to initialize these:
dateButton = (Button) findViewById(R.id.registration_date);
rented = (CheckBox) findViewById(R.id.isrented);
rented.setOnCheckedChangeListener(this);
Furthmore, we are going to disable the date button when the activity is created:
dateButton.setEnabled(false);
We would now like to engage the checkbox rented. First, implement the OnCheckedChangeListener interface:
public class ResidenceActivity extends Activity implements TextWatcher, OnCheckedChangeListener
{
This will require the following import:
import android.widget.CompoundButton.OnCheckedChangeListener;
and this is the implementation:
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
Log.i(this.getClass().getSimpleName(), "rented Checked");
residence.rented = isChecked;
}
Run the app and test the check button to ensure that the message, rented Checked, is logged when you check and uncheck the box.
This completes the class. Here is the complete code to this stage:
package org.wit.myrent.activities;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import org.wit.myrent.R;
import org.wit.myrent.models.Residence;
public class ResidenceActivity extends AppCompatActivity implements TextWatcher, OnCheckedChangeListener{
private EditText geolocation;
private Residence residence;
private CheckBox rented;
private Button dateButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_residence);
geolocation = (EditText) findViewById(R.id.geolocation);
residence = new Residence();
geolocation.addTextChangedListener(this);
dateButton = (Button) findViewById(R.id.registration_date);
dateButton.setEnabled(false);
rented = (CheckBox) findViewById(R.id.isrented);
rented.setOnCheckedChangeListener(this);
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
residence.setGeolocation(editable.toString());
}
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
Log.i(this.getClass().getSimpleName(), "rented Checked");
residence.rented = isChecked;
}
}
Run the app and check that the layout presented is as expected, something like that shown in Figure 1.
Run the app and use the debugger to ensure data is being transmitted to and from the Residence object.
Examine the state of the variables such as:
Press the Run to Cursor toolbar icon to run to completion.
Here is what we have achieved in this topic:
Added widgets to the layout
Added a listener in the controller to detected changes in the UI checkbox state and transmit any state changes to the model Residence object
Added a date field to the model and intialized this at the time a residence object created which represents the registration date of the property with the MyRent app.
Described how to conduct a simple test using the debugger to verify that the listeners operate correctly and that UI data transmission takes place successfully in both directions between model and UI.
The application at the end of this lab is available for download from GitHub: android-myrent-2017
Select Releases, followed by V1.0.